package com.authine.cloudpivot.app.openapi.config;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.ExtendBalancer;
import com.alibaba.cloud.nacos.ribbon.NacosRule;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.cola.dto.RequestContext;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.google.common.primitives.Ints;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @author carter
 * create_date  2020/11/3 16:40
 * description ribbon的负载均衡规则
 */
@Configuration
@Slf4j
public class LocalDebugRibbonConfig {


    @Bean
    public IRule ribbonRule() {
        return new DebugRequestRule();
    }


    @Slf4j
    public static class DebugRequestRule extends NacosRule implements EnvironmentAware {

        @Autowired
        private NacosDiscoveryProperties nacosDiscoveryProperties;

        private Environment environment;

        @Override
        public void setEnvironment(Environment environment) {
            this.environment = environment;
            log.info("======安装环境变量对象成功");
        }

        @Override
        public Server choose(Object key) {
            AtomicReference<String> requestIdR = new AtomicReference<>();
            AtomicReference<String> debugRequestIdR = new AtomicReference<>();
            AtomicReference<String> loginIdR = new AtomicReference<>();
            AtomicReference<String> loginNameR = new AtomicReference<>();
            Map<String, String> map = new HashMap<>(4);
            if (key instanceof Map) {
                map = (Map<String, String>) key;
            }
            debugRequestIdR.set(map.getOrDefault(RequestContext.KeyDict.DEBUG_REQUEST_ID_KEY, ""));
            requestIdR.set(map.getOrDefault(RequestContext.KeyDict.REQUEST_ID_KEY, ""));
            loginIdR.set(map.getOrDefault(RequestContext.KeyDict.LOGIN_ID_KEY, ""));
            loginNameR.set(map.getOrDefault(RequestContext.KeyDict.LOGIN_NAME_KEY, ""));

            try {
                String clusterName = nacosDiscoveryProperties.getClusterName();
                DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) this.getLoadBalancer();
                String serviceName = map.getOrDefault("serviceId", loadBalancer.getName());
                NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
                List<Instance> instances = namingService.selectInstances(serviceName, true);
                if (CollectionUtils.isEmpty(instances)) {
                    log.warn("no instance in service {}", serviceName);
                    return null;
                } else {
                    List<Instance> instancesToChoose = instances;
                    if (StringUtils.isNotBlank(clusterName)) {
                        List<Instance> sameClusterInstances = instances.stream().filter((instancex) -> Objects.equals(clusterName, instancex.getClusterName())).collect(Collectors.toList());
                        if (!CollectionUtils.isEmpty(sameClusterInstances)) {
                            instancesToChoose = sameClusterInstances;
                        } else {
                            log.warn("A cross-cluster call occurs，serviceName = {}, clusterName = {}, instance = {}", serviceName, clusterName, instances);
                        }
                    }
                    Instance instance;
                    if (key instanceof Map) {
                        instance = instancesToChoose.stream().filter(item -> {
                            Optional<Map<String, String>> stringStringMap = Optional.ofNullable(item).map(Instance::getMetadata);
                            String requestId = stringStringMap.map(i -> i.get(RequestContext.KeyDict.REQUEST_ID_KEY)).orElse(null);
                            String debugRequestId = stringStringMap.map(i -> i.get(RequestContext.KeyDict.DEBUG_REQUEST_ID_KEY)).orElse(null);
                            String userId = stringStringMap.map(i -> i.get(RequestContext.KeyDict.LOGIN_ID_KEY)).orElse(null);
                            String loginName = stringStringMap.map(i -> i.get(RequestContext.KeyDict.LOGIN_NAME_KEY)).orElse(null);
                            return
                                    Objects.equals(debugRequestIdR.get(), debugRequestId)
                                            || Objects.equals(requestIdR.get(), requestId)
                                            || Objects.equals(loginIdR.get(), userId)
                                            || Objects.equals(loginNameR.get(), loginName);
                        }).findAny().orElse(getInstanceExcludeSpecialMetadata(instancesToChoose));
                    } else {
                        //如果没有特殊元数据配置，则从不含有特殊元数据配置的负载列表中选择
                        instance = getInstanceExcludeSpecialMetadata(instancesToChoose);
                    }
                    log.info("key：{} 选择的路由信息：{}", key, instance.getInstanceId());
                    return new NacosServer(instance);
                }
            } catch (Exception var9) {
                log.warn("Nacos Rule error", var9);
                return null;
            }
        }

        private Instance getInstanceExcludeSpecialMetadata(List<Instance> instancesToChoose) {
            List<Instance> instanceList = instancesToChoose.stream().filter(item -> {
                Map<String, String> metadata = item.getMetadata();
                return !(
                        metadata.containsKey(RequestContext.KeyDict.REQUEST_ID_KEY)
                                || metadata.containsKey(RequestContext.KeyDict.DEBUG_REQUEST_ID_KEY)
                                || metadata.containsKey(RequestContext.KeyDict.LOGIN_ID_KEY)
                                || metadata.containsKey(RequestContext.KeyDict.LOGIN_NAME_KEY));
            }).collect(Collectors.toList());

            if (instanceList.isEmpty()) {
                instanceList = instancesToChoose;
            }
            return changeAndCopyInstance(ExtendBalancer.getHostByRandomWeight2(instanceList));
        }

        /**
         * 复制一个实例，修改它的ip和端口为NodeIp和端口 (兼容没有配置的情况)
         *
         * @param instance 实例信息
         * @return 转换之后的实例信息
         */
        private Instance changeAndCopyInstance(Instance instance) {

            //如果是本地环境，即带了调测标记的；
            if (!isLocalProcess()) {
                //如果不是本地，不需要走nodeIP和NodePort的方式；
                return instance;
            }

            Map<String, String> metadata = instance.getMetadata();

            String nodeIp = metadata.get("nodeIp");
            String nodePortStr = metadata.get("nodePort");

            if (!(StringUtils.isNotBlank(nodeIp) && StringUtils.isNumeric(nodePortStr))) {
                return instance;
            }

            Instance instanceCopy = new Instance();
            BeanUtils.copyProperties(instance, instanceCopy);

            instanceCopy.setIp(nodeIp);
            int nodePort = Ints.tryParse(nodePortStr);
            instanceCopy.setPort(nodePort);

            return instanceCopy;

        }

        /**
         * 如果能从环境变量中获取到这些配置参数之一，认为是在开发人员本地进行调测
         *
         * @return true 本地 false 非本地
         */
        private boolean isLocalProcess() {
            return Optional.ofNullable(environment).map(item -> {
                //含有调测标记
                return item.containsProperty("local.debug.loginId")
                        || item.containsProperty("local.debug.requestId")
                        || item.containsProperty("local.debug.loginName")
                        || item.containsProperty("local.debug.debug_request_id")
                        || item.containsProperty("spring.cloud.nacos.discovery.metadata.loginId")
                        || item.containsProperty("spring.cloud.nacos.discovery.metadata.loginName")
                        || item.containsProperty("spring.cloud.nacos.discovery.metadata.requestId")
                        || item.containsProperty("spring.cloud.nacos.discovery.metadata.debug_request_id")
                        ;

            }).orElse(false);

        }


    }

}